﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using OpenNETCF;
using System.Runtime.InteropServices;
using System.Text;
using System.ComponentModel;

namespace OpenNETCF
{
    public class PlatformInfo
    {
        public TargetHardware Model { get; private set; }
        public string PlatformName { get; private set; }
        public string OEMInfo { get; private set; }
        public string DeviceID { get; private set; }

        public PlatformInfo()
        {
            DetermineCurrentHardware();
        }

        private void DetermineCurrentHardware()
        {
            PlatformName = GetPlatformName();
            OEMInfo = GetOEMInfo();
            DeviceID = GetDeviceID();

            switch (PlatformName)
            {
                case "PocketPC":
                    DeterminePocketPCModel();
                    break;
                case "SYMBOL WinCE":
                    DetermineSymbolModel();
                    break;
            }

            if (Model == TargetHardware.Unknown)
            {
                // now maybe pull registry info, screen dimensions, etc if it gets that far
                var deviceID = GetDeviceID();
            }
        }

        private void DetermineSymbolModel()
        {
            switch (OEMInfo)
            {
                case "SYMBOL MC9090G":
                    Model = TargetHardware.MC9090;
                    break;                
            }
        }

        private void DeterminePocketPCModel()
        {
            switch (OEMInfo)
            {
                case "Microsoft DeviceEmulator":
                    Model = TargetHardware.PPC2003SE_Emulator;
                    break;
            }
        }

        private string GetPlatformName()
        {
            // ---- known/test return values ----
            // PPC2003SE Emulator:
            //  "PocketPC"
            // Symbol MC9090:
            //  "SYMBOL WinCE"

            var name = new StringBuilder(256);
            SystemParametersInfo(SPI_GETPLATFORMTYPE, name.Capacity, name, 0);

            return name.ToString();
        }

        private string GetOEMInfo()
        {
            // ---- known/test return values ----
            // PPC2003SE Emulator:
            //  "Microsoft DeviceEmulator"
            // Symbol MC9090:
            //  "SYMBOL MC9090G"

            var info = new StringBuilder(256);
            SystemParametersInfo(SPI_GETOEMINFO, info.Capacity, info, 0);

            return info.ToString();
        }

        private string GetDeviceID()
        {
            // ---- known/test return values ----
            // PPC2003SE Emulator:
            //  "4D006900630072006F0073006F0066007400200044006500760069006300650045006D0075006C00610074006F0072000000-4E4B4E6F4B69746C0000000000000000"

            var bufferSize = 256;
            var buffer = new byte[bufferSize];
            var outputSize = 0;

            // set the buffer size in the struct (first 4 bytes of the buffer)
            Buffer.BlockCopy(BitConverter.GetBytes(bufferSize), 0, buffer, 0, 4);

            while(true) // yeah, I used a 'while(true)'.  Get over it.
            {
                if (KernelIoControl(IOCTL_HAL_GET_DEVICEID, IntPtr.Zero,
                    0, buffer, bufferSize, ref outputSize))
                {
                    // call was successful - we're done
                    break;
                }

                switch ((APIResult)Marshal.GetLastWin32Error())
                {
                    case APIResult.NotSupported:
                        throw new NotSupportedException(
                            "IOCTL_HAL_GET_DEVICEID is not supported on this device");

                    case APIResult.InsufficientBuffer:
                        // not enough of a buffer.  The required size will be in the first 4 bytes of the result
                        // re-size the buffer and re-try
                        bufferSize = BitConverter.ToInt32(buffer, 0);
                        buffer = new byte[bufferSize];

                        // set the new buffer size in the struct (first 4 bytes of the buffer)
                        Buffer.BlockCopy(BitConverter.GetBytes(bufferSize), 0, buffer, 0, 4);
                        break;

                    default:
                        throw new Win32Exception(Marshal.GetLastWin32Error(), "IOCTL Failed");
                }
            }

            int presetOffset = BitConverter.ToInt32(buffer, 0x4);
            int presetOffsetSize = BitConverter.ToInt32(buffer, 0x8);
            int platformOffset = BitConverter.ToInt32(buffer, 0xc);
            int platformOffsetSize = BitConverter.ToInt32(buffer, 0x10);
            var sb = new StringBuilder();

            for (int i = presetOffset; i < presetOffset + presetOffsetSize; i++)
            {
                sb.Append(String.Format("{0:X2}", buffer[i]));
            }

            sb.Append("-");

            for (int i = platformOffset; i < platformOffset + platformOffsetSize; i++)
            {
                sb.Append(String.Format("{0:X2}", buffer[i]));
            }

            return sb.ToString();
        }

        private const string COREDLL = "coredll.dll";
        private const int SPI_GETPLATFORMTYPE = 257;
        private const int SPI_GETOEMINFO = 258;
        private const int METHOD_BUFFERED = 0;
        private const int FILE_ANY_ACCESS = 0;
        private const int FILE_DEVICE_HAL = 0x00000101;

        private enum APIResult
        {
            NotSupported = 0x32,
            InsufficientBuffer = 0x7A
        }

        private const int IOCTL_HAL_GET_DEVICEID =
            ((FILE_DEVICE_HAL) << 16) | ((FILE_ANY_ACCESS) << 14)
            | ((21) << 2) | (METHOD_BUFFERED);

        [DllImport(COREDLL, SetLastError = true)]
        private static extern bool SystemParametersInfo(
            int uiAction, 
            int uiParam, 
            StringBuilder pvParam, 
            uint fWinIni);

        [DllImport(COREDLL, SetLastError = true)]
        private static extern bool KernelIoControl(
            int dwIoControlCode,
            IntPtr lpInBuf, 
            int nInBufSize, 
            byte[] lpOutBuf,
            int nOutBufSize, 
            ref int lpBytesReturned);

    }
}
